package com.hzgj.bcl.soa;

import com.hzgj.bcl.soa.util.Constants;
import com.hzgj.bcl.util.jmx.Management;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ChainStack extends Interceptor {
    public static final int ABOVE = 1; // used by insertInterceptor()
    public static final int BELOW = 2; // used by insertInterceptor()

    protected static final String max_list_print_size = "max-list-print-size";

    protected Interceptor top_interceptor;
    protected Interceptor bottom_interceptor;
    protected volatile boolean stopped = true;

    public List<Interceptor> getInterceptors() {
        List<Interceptor> v = new ArrayList<>(15);
        Interceptor p = top_interceptor;
        while (p != null) {
            v.add(p);
            p = p.getDownInterceptor();
        }
        return v;
    }

    /**
     * Prints the names of the Interceptors, from the bottom to top.
     */

    public String printInterceptorSpec() {
        StringBuilder sb = new StringBuilder();
        List<Interceptor> interceptors = getInterceptors();

        if (interceptors == null || interceptors.isEmpty()) {
            return null;
        }
        boolean first_colon_printed = false;

        Collections.reverse(interceptors);
        for (Interceptor prot : interceptors) {
            String prot_name = prot.getClass().getName();
            int index = prot_name.indexOf(Constants.PREFIX);
            if (index >= 0) {
                prot_name = prot_name.substring(Constants.PREFIX.length());
            }
            if (first_colon_printed) {
                sb.append(":");
            } else {
                first_colon_printed = true;
            }

            sb.append(prot_name);
        }
        return sb.toString();
    }

    /**
     * Adds a Interceptor at the tail of the Interceptor list
     *
     * @param interceptor
     * @return
     */
    public ChainStack addInterceptor(Interceptor interceptor) {
        if (interceptor == null) {
            return this;
        }
        interceptor.setChain(this);
        interceptor.setUpInterceptor(this);
        if (bottom_interceptor == null) {
            top_interceptor = bottom_interceptor = interceptor;
            return this;
        }
        interceptor.setDownInterceptor(top_interceptor);
        interceptor.getDownInterceptor().setUpInterceptor(interceptor);
        top_interceptor = interceptor;
        Management.register(interceptor, null);
        return this;
    }

    /**
     * Adds a list of Interceptors
     *
     * @param prots
     * @return
     * @since 2.11
     */
    public ChainStack addInterceptors(Interceptor... prots) {
        if (prots != null) {
            for (Interceptor prot : prots) {
                addInterceptor(prot);
            }
        }
        return this;
    }

    /**
     * Adds a list of Interceptors
     *
     * @param prots
     * @return
     * @since 2.1
     */
    public ChainStack addInterceptors(List<Interceptor> prots) {
        if (prots != null) {
            for (Interceptor prot : prots) {
                addInterceptor(prot);
            }
        }
        return this;
    }


    /**
     * Inserts an already created (and initialized) Interceptor into the Interceptor list. Sets the links
     * to the Interceptors above and below correctly and adjusts the linked list of Interceptors accordingly.
     * Note that this method may change the value of top_interceptor or bottom_interceptor.
     *
     * @param prot          The Interceptor to be inserted. Before insertion, a sanity check will ensure that none
     *                      of the existing Interceptors have the same name as the new Interceptor.
     * @param position      Where to place the Interceptor with respect to the neighbor_prot (ABOVE, BELOW)
     * @param neighbor_prot The name of the neighbor Interceptor. An exception will be thrown if this name
     *                      is not found
     * @throws Exception Will be thrown when the new Interceptor cannot be created, or inserted.
     */
    public void insertInterceptor(Interceptor prot, int position, String neighbor_prot) throws Exception {
        if (neighbor_prot == null) {
            throw new IllegalArgumentException("neighbor_prot is null");
        }
        if (position != ChainStack.ABOVE && position != ChainStack.BELOW) {
            throw new IllegalArgumentException("position has to be ABOVE or BELOW");
        }

        Interceptor neighbor = findInterceptor(neighbor_prot);
        if (neighbor == null) {
            throw new IllegalArgumentException("Interceptor " + neighbor_prot + " not found in " + printInterceptorSpec());
        }

        insertInterceptorInStack(prot, neighbor, position);
    }

    public void insertInterceptorInStack(Interceptor prot, Interceptor neighbor, int position) {
        // connect to the Interceptor layer below and above
        if (position == ChainStack.BELOW) {
            prot.setUpInterceptor(neighbor);
            Interceptor below = neighbor.getDownInterceptor();
            prot.setDownInterceptor(below);
            if (below != null) {
                below.setUpInterceptor(prot);
            }
            neighbor.setDownInterceptor(prot);
        } else { // ABOVE is default
            Interceptor above = neighbor.getUpInterceptor();
            checkAndSwitchTop(neighbor, prot);
            prot.setUpInterceptor(above);
            if (above != null) {
                above.setDownInterceptor(prot);
            }
            prot.setDownInterceptor(neighbor);
            neighbor.setUpInterceptor(prot);
        }
    }

    private void checkAndSwitchTop(Interceptor oldTop, Interceptor newTop) {
        if (oldTop == top_interceptor) {
            top_interceptor = newTop;
            top_interceptor.setUpInterceptor(this);
        }
    }

    public void insertInterceptor(Interceptor prot, int position, Class<? extends Interceptor> neighbor_prot) throws Exception {
        if (neighbor_prot == null) {
            throw new IllegalArgumentException("neighbor_prot is null");
        }
        if (position != ChainStack.ABOVE && position != ChainStack.BELOW) {
            throw new IllegalArgumentException("position has to be ABOVE or BELOW");
        }

        Interceptor neighbor = findInterceptor(neighbor_prot);
        if (neighbor == null) {
            throw new IllegalArgumentException("Interceptor \"" + neighbor_prot + "\" not found in " + chain.printInterceptorSpec());
        }

        insertInterceptorInStack(prot, neighbor, position);
    }


    public void insertInterceptor(Interceptor prot, int position, Class<? extends Interceptor>... neighbor_prots) throws Exception {
        if (neighbor_prots == null) {
            throw new IllegalArgumentException("neighbor_prots is null");
        }
        if (position != ChainStack.ABOVE && position != ChainStack.BELOW) {
            throw new IllegalArgumentException("position has to be ABOVE or BELOW");
        }

        Interceptor neighbor = findInterceptor(neighbor_prots);
        if (neighbor == null) {
            throw new IllegalArgumentException("Interceptor \"" + Arrays.toString(neighbor_prots) + "\" not found in " + chain.printInterceptorSpec());
        }
        insertInterceptorInStack(prot, neighbor, position);
    }


    public void insertInterceptorAtTop(Interceptor prot) {
        if (prot == null) {
            throw new IllegalArgumentException("prot needs to be non-null");
        }

        // check if prot already exists (we cannot have more than 1 Interceptor of a given class)
        Class<? extends Interceptor> clazz = prot.getClass();
        Interceptor existing_instance = findInterceptor(clazz);
        if (existing_instance != null) {
            return;
        }

        top_interceptor.up_int = prot;
        prot.down_int = top_interceptor;
        prot.up_int = this;
        top_interceptor = prot;
        log.debug("inserted " + prot + " at the top of the chainStack");
    }

    public void insertInterceptorAtBottom(Interceptor interceptor) {
        Interceptor tmp = top_interceptor;
        Interceptor last_one = null;
        while (tmp != null) {
            last_one = tmp;
            tmp = tmp.getDownInterceptor();
        }
        last_one.down_int = interceptor;
        interceptor.up_int = last_one;
        bottom_interceptor = interceptor;
    }


    /**
     * Removes a Interceptor from the chainStack. Stops the Interceptor and readjusts the linked lists of
     * Interceptors.
     *
     * @param prot_name The name of the Interceptor. Since all Interceptor names in a chainStack have to be unique
     *                  (otherwise the chainStack won't be created), the name refers to just 1 Interceptor.
     * @throws Exception Thrown if the Interceptor cannot be stopped correctly.
     */
    public Interceptor removeInterceptor(String prot_name) {
        if (prot_name == null) {
            return null;
        }
        return removeInterceptor(findInterceptor(prot_name));
    }

    public void removeInterceptors(String... Interceptors) {
        for (String Interceptor : Interceptors) {
            removeInterceptor(Interceptor);
        }
    }


    public Interceptor removeInterceptor(Class... Interceptors) {
        Interceptor retval = null;
        if (Interceptors != null) {
            for (Class cl : Interceptors) {
                Interceptor tmp = removeInterceptor(cl);
                if (tmp != null) {
                    retval = tmp;
                }
            }
        }

        return retval;
    }


    public Interceptor removeInterceptor(Class prot) {
        if (prot == null) {
            return null;
        }
        return removeInterceptor(findInterceptor(prot));
    }

    public Interceptor removeInterceptor(Interceptor prot) {
        if (prot == null) {
            return null;
        }
        Interceptor above = prot.getUpInterceptor(), below = prot.getDownInterceptor();
        checkAndSwitchTop(prot, below);
        if (above != null) {
            above.setDownInterceptor(below);
        }
        if (below != null) {
            below.setUpInterceptor(above);
        }
        prot.setUpInterceptor(null);
        prot.setDownInterceptor(null);
        try {
            prot.stop();
        } catch (Throwable t) {
            log.error("failed stopping " + prot.getName() + ": " + t);
        }
        try {
            prot.destroy();
        } catch (Throwable t) {
            log.error("failed destroying " + prot.getName() + ": " + t);
        }
        return prot;
    }


    /**
     * Returns a given Interceptor or null if not found
     */
    public Interceptor findInterceptor(String name) {
        Interceptor tmp = top_interceptor;
        String prot_name;
        while (tmp != null) {
            prot_name = tmp.getName();
            if (prot_name != null && prot_name.equals(name)) {
                return tmp;
            }
            tmp = tmp.getDownInterceptor();
        }
        return null;
    }

    public Interceptor getBottomInterceptor() {
        Interceptor curr_prot = this;
        while (curr_prot != null && curr_prot.getDownInterceptor() != null) {
            curr_prot = curr_prot.getDownInterceptor();
        }
        return curr_prot;
    }

    public Interceptor getTopInterceptor() {
        return top_interceptor;
    }

    public Interceptor findInterceptor(Class<?> clazz) {
        Interceptor tmp = top_interceptor;
        while (tmp != null) {
            Class<?> protClass = tmp.getClass();
            if (clazz.isAssignableFrom(protClass)) {
                return tmp;
            }
            tmp = tmp.getDownInterceptor();
        }
        return null;
    }

    /**
     * Finds the first Interceptor of a list and returns it. Returns null if no Interceptor can be found
     *
     * @param classes A list of Interceptor classes to find
     * @return Interceptor The Interceptor found
     */
    public Interceptor findInterceptor(Class<?>... classes) {
        for (Class<?> clazz : classes) {
            Interceptor prot = findInterceptor(clazz);
            if (prot != null) {
                return prot;
            }
        }
        return null;
    }

    /**
     * Replaces one Interceptor instance with another. Should be done before the chainStack is connected
     *
     * @param existing_prot
     * @param new_prot
     */
    public void replaceInterceptor(Interceptor existing_prot, Interceptor new_prot) throws Exception {
        Interceptor up_neighbor = existing_prot.getUpInterceptor(), down_neighbor = existing_prot.getDownInterceptor();

        new_prot.setUpInterceptor(existing_prot.getUpInterceptor());
        new_prot.setDownInterceptor(existing_prot.getDownInterceptor());
        up_neighbor.setDownInterceptor(new_prot);
        if (down_neighbor != null) {
            down_neighbor.setUpInterceptor(new_prot);
        }

        existing_prot.setDownInterceptor(null);
        existing_prot.setUpInterceptor(null);
        existing_prot.stop();
        existing_prot.destroy();

        if (new_prot.getUpInterceptor() == this) {
            top_interceptor = new_prot;
        }
    }

    @Override
    public void start() throws Exception {
        if (!stopped) {
            return;
        }
        List<Interceptor> Interceptors = getInterceptors();
        Collections.reverse(Interceptors);
        for (Interceptor prot : Interceptors) {
            prot.start();
        }
        stopped = false;
    }

    @Override
    public void stop() {
        if (stopped) {
            return;
        }
        getInterceptors().forEach(Interceptor::stop);
        stopped = true;
    }


    @Override
    public void destroy() {
        if (top_interceptor != null) {
            getInterceptors().forEach(Interceptor::destroy);
        }
    }

    @Override
    public String getName() {
        return "InterceptorStack";
    }

    @Override
    public Object down(Event evt) {
        if (top_interceptor != null) {
            return top_interceptor.down(evt);
        }
        return null;
    }


    /**
     * Keeps track of the number os times init()/destroy() and start()/stop have been called. The variables
     * init_count and start_count are incremented or decremented accoordingly. Note that this class is not synchronized
     */
    public static class RefCounter {
        private short init_count = 0;
        private short start_count = 0;

        public RefCounter(short init_count, short start_count) {
            this.init_count = init_count;
            this.start_count = start_count;
        }

        public short getInitCount() {
            return init_count;
        }

        public short getStartCount() {
            return start_count;
        }

        /**
         * Increments init_count, returns the old value before incr
         *
         * @return
         */
        public short incrementInitCount() {
            return init_count++;
        }

        public short decrementInitCount() {
            init_count = (short) Math.max(init_count - 1, 0);
            return init_count;
        }

        public short decrementStartCount() {
            start_count = (short) Math.max(start_count - 1, 0);
            return start_count;
        }

        public short incrementStartCount() {
            return start_count++;
        }

        @Override
        public String toString() {
            return "init_count=" + init_count + ", start_count=" + start_count;
        }
    }
}
